room 是一個簡單好用的本地資料庫,比起一般的 sqlite,它更為直覺去使用,而且也跟 live data 有著良好的互動性。
在 room database 中分為三個主要的 class 部分
一個 entity 必須要有 table name 和一個 primary key
@Entity(tableName = "comment_table")
data class DataEntity(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") var id: Int,
@ColumnInfo(name = "content") var content: String
)
在筆者的專案中,還會常需要用到 foreign key 跟索引,如果加上去,會像這樣子
@Entity(
tableName = "comment_table",
foreignKeys = [
ForeignKey(
entity = AuthorEntity::class,
parentColumns = arrayOf("_id"),
childColumns = arrayOf("author_id"),
onDelete = ForeignKey.CASCADE),
ForeignKey(
entity = DateEntity::class,
parentColumns = arrayOf("_id"),
childColumns = arrayOf("date_id"),
onDelete = ForeignKey.CASCADE),
indices = [Index(value = ["_id"])]
)
data class CommentEntity(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "_id") var id: Int,
@ColumnInfo(name = "author_id") var authorId: Int,
@ColumnInfo(name = "date_id") var dateId: Int,
@ColumnInfo(name = "content") var content: String
)
透過 foreign key 的建立可以完善資料庫的正規化,
foreign key 填入
@Dao
interface CommentDao {
@Query("SELECT * FROM comment_table")
fun getAllComments(): LiveData<List<CategoryEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertComment(comment: CommentEntity)
}
所有的 CRUD 方法,都會放在這裡給其他類別使用。
如果想要新增更多,就加入更多的 function 並且在 @Query() 打入想要的 sqlite 語法。
在 @Insert 裡面填入如果發生資料重複/衝突時的處理方式,筆者常用的只有兩個
如果只想要抓特定的資料也是可以的,用冒號代表其引數
@Query("SELECT * FROM comment_table WHERE date_id IN (:date)")
fun getDailyComments(date: Int): LiveData<List<ExpenseEntity>>
只抓 dateId = 傳入的 date 的資料
@Database(
entities = [
CommentEntity::class,
DateEntity::class,
AuthorEntity::class],
version = 1
)
abstract class AccountingDatabase : RoomDatabase(){
//取得 dao 的方法
abstract fun getAccountingDao(): AccountingDao
//官方推薦的 Singleton 寫法,因為實體的產生很耗資源,而且也不需要多個資料庫實體
companion object {
@Volatile
private var INSTANCE: AccountingDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): AccountingDatabase {
return INSTANCE ?: synchronized(this) {
val instance =
Room.databaseBuilder(
context.applicationContext,
AccountingDatabase::class.java,
"accounting_database"
).fallbackToDestructiveMigration()
.allowMainThreadQueries()
.addCallback(ItemDatabaseCallback(scope))
.build()
INSTANCE = instance
// return instance
instance
}
}
}
}
這一段是從 codelab 上複製的
一個繼承於 RoomDatabase 的抽象類別,其中如果 database 為 null 的話就建立一個
class Repository(private val commentDao: CommentDao) {
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun insertItem(comment: CommentEntity) {
accountingDao.insertComment(comment)
}
fun getAllComments(): LiveData<List<CommentEntity>>{
return commentDao.getAllComments()
}
}
在 Repository 中加入 public 的方法給外部使用
上述都完成了就可以來讓其他 class 取得資料
例如 view model:
class CommentsViewModel(application: Application): AndroidViewModel(application){
private val repository: Repository
var allComments: LiveData<List<CommentEntity>>
init {
val listDao = AccountingDatabase.getDatabase(application, viewModelScope).getAccountingDao()
repository = Repository(listDao)
allComments= repository.getAllComments
}
fun insertComment(comment: CommentEntity) = viewModelScope.launch(Dispatchers.IO) {
repository.insertComment(comment)
}
}
另外,如上圖所示,要做 insert 時要使用 coroutines 確保安全
其實就是不斷的寫 fun 讓 class 與 class 之間做溝通,多看文章跟多寫幾次不難懂的!